#include "stdafx.h"
#include "HXPlayer.h"
#include "HXAlive.h"
#include "HXDirector.h"
#include "HXLink.h"
#include "HXTalk.h"
#include "HXWindow.h"

//=============================================================================

HXMain g_main;						// main application variables
HXMainPaths g_paths;				// working paths
HXMouse g_mouse;					// records mouse clicks

//=============================================================================

//! Constructor.
HXMain::HXMain()
: hAuxThread(0),
  hSpeechThread(0),
  doAuxExit(false),
  doSpeechExit(false),
  bSpeakNow(false),
  bBlock(false)
{
	hSpeechMutex = CreateMutex(NULL, FALSE, NULL);
}

//! Close all threads and resources.
void HXMain::Destroy()
{
	if (hAuxThread)
	{	
		doAuxExit = true;
		WaitForSingleObject(hAuxThread, INFINITE);
		CloseHandle(hAuxThread);
		hAuxThread = NULL;
		doAuxExit = false;
	}
	if (hSpeechThread)
	{
		doSpeechExit = true;
		bSpeakNow = true;
		WaitForSingleObject(hSpeechThread, INFINITE);
		CloseHandle(hSpeechThread);
		hSpeechThread = NULL;
		doSpeechExit = false;
		bSpeakNow = false;
	}
	CloseHandle(hSpeechMutex);
}

//-----------------------------------------------------------------------------

//! Returns the working directory at program start (as working directory changes during program use).
wchar_t* HXMainPaths::GetStartupPath()
{
	return m_startupPath;
}

//! Returns the filename of the starting configuration (which was specified as a command-line argument).
wchar_t* HXMainPaths::GetStartupFile()
{
	return m_startupFile;
}


//! Returns the directory of the last file passed to SplitFilePath().
wchar_t* HXMainPaths::GetLastFilePath()
{
	return m_lastFilePath;
}

//! Returns the name of the last file passed to SplitFilePath().
wchar_t* HXMainPaths::GetLastFileName()
{
	return m_lastFileName;
}

//! Clears the last file name.
void HXMainPaths::ResetLastFile()
{
	m_lastFilePath[0] = NULL;
	m_lastFileName[0] = NULL;
}

//! Set current working path for this program.
void HXMainPaths::SetWorkingPath(const wstring& path)
{
	SetCurrentDirectory(path.c_str());
	g_link.SetWorkingPath(path); // Synapse share...
}

//! Separates a (valid) path into its directory and file name components.
bool HXMainPaths::SplitFilePath(wstring fullArg, wstring* pFileDir, wstring* pFileName, bool bSplitOnly)
{
	wchar_t fullArgChars[MAX_PATH];
	wchar_t fileDirChars[MAX_PATH];
	wchar_t fileNameChars[MAX_PATH];
	bool success = false;
	wcscpy_s(fullArgChars, MAX_PATH, fullArg.c_str());
	PathUnquoteSpaces(fullArgChars);
	fullArg = wstring(fullArgChars);
	PathSearchAndQualify(fullArg.c_str(), fileDirChars, MAX_PATH);
	if (bSplitOnly || PathFileExists(fileDirChars))
	{
		success = true;
	}
	else
	{
		fullArg.append(L".hxml");
		PathSearchAndQualify(fullArg.c_str(), fileDirChars, MAX_PATH);
		if (PathFileExists(fileDirChars))
		{
			success = true;
		}
	}
	if (success)
	{
		PathRemoveFileSpec(fileDirChars);
		if (pFileDir)
			*pFileDir = wstring(fileDirChars);
		wcscpy_s(fileNameChars, MAX_PATH, fullArg.c_str());
		PathStripPath(fileNameChars);
		if (pFileName)
			*pFileName = wstring(fileNameChars);
	}
	return success;
}

//! Store last used paths.
void HXMainPaths::SetLast(wstring& fileDir, wstring& fileName)
{
	wcscpy_s(m_lastFilePath, MAX_PATH, fileDir.c_str());
	wcscpy_s(m_lastFileName, MAX_PATH, fileName.c_str());
}

//-----------------------------------------------------------------------------

//! Application entry point.
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	#ifdef _DEBUG
	_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
	//_CrtSetBreakAlloc(13023);
	#endif

	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	MSG msg;
	HACCEL hAccelTable;

	// Initialize global strings
	LoadString(hInstance, IDS_APP_TITLE, g_main.szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_HXPLAYER, g_main.szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);
	g_main.hInst = hInstance; 

	// Command-line processing
	wchar_t *token, *next_token;
	wchar_t seps[] = L" \t\n";
    token = wcstok_s(lpCmdLine, seps, &next_token);
    while (token)
    {
	  if (token[0] == '-')
	  {
		  switch (token[1])
		  {
			  case L'd':
				  g_config.enableDepthShaders = true;
				  break;
			  case L'l':
				  g_config.disableShaders = true;
				  break;
			  case L'm':
			  {
				  token = wcstok_s(NULL, seps, &next_token);
				  int value = 0;
				  if (token)
				  {
					  value = WStringToInt(wstring(token));
					  if (value > 0)
					  {
						 g_config.multisample = value;
					  }
				  }
				  if (!(value > 0))
				  {
  					  WNotify(NULL, L"ERROR: \"-m\" command-line option must have valid integer argument (> 0). Using previous value (%d) instead.", g_config.multisample);
				  }
				  break;
			  }
			  case L's':
			  {
				  token = wcstok_s(NULL, seps, &next_token);
				  wstring tempString;
				  if (token)
				  {
					  tempString = wstring(token);
					  if (!tempString.empty())
					  {
						  wchar_t absolutePath[MAX_PATH];
						  PathSearchAndQualify(token, absolutePath, MAX_PATH);
						  g_config.shaderDir.assign(absolutePath);
					  }
				  }
				  if (tempString.empty())
				  {
  					  WNotify(NULL, L"ERROR: \"-s\" command-line option must be followed by a string. Using previous value (\"%s\") instead.", g_config.shaderDir.c_str());
				  }
				  break;
			  }
			  default:
			  {
				  WNotify(NULL, L"ERROR: Unknown command-line option (\"-%c\"). Known options are '-d' (use depth shaders), '-l' (legacy mode), '-m' (multisampling), and '-s' (default shader directory).", token[1]);
				  break;
			  }
		  }
		  token = wcstok_s(NULL, seps, &next_token);
	  }
	  else
	  {
		  bool bAppended = false;
		  while (token)
		  {
			  if (bAppended) 
			  {
				  wcscat_s(g_paths.GetStartupFile(), MAX_PATH, L" ");
			  }
			  else
			  {
				  bAppended = true;
			  }
			  wcscat_s(g_paths.GetStartupFile(), MAX_PATH, token);
			  token = wcstok_s(NULL, seps, &next_token);
		  }
	  }
	}	  
	
	// Perform window initialization
	if (!g_window.Create(nCmdShow) || !g_director.Run())
	{
		ExitProc();
		return FALSE;
	}
	g_alive.Run(g_director.GetDisplay());

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_HXPLAYER));

	// Perform Synapse initialization
	if (!g_link.Connect())
	{
	   WNotify(NULL, L"ERROR: Synapse link could not be established.\nPlease close all other Head X instances and plug-ins.");
	   ExitProc();
	   return FALSE;
    }
	else
	if (g_link.GetHeadNumber() > 1)
	{
		TCHAR buffer[MAX_LOADSTRING];
		swprintf_s(buffer, MAX_LOADSTRING, L"%s (#%u)", g_main.szTitle, g_link.GetHeadNumber());
		SetWindowText(g_window.GetMainHandle(), buffer);
    }

	// Perform TTS initialization
	if (!(g_main.hSpeechThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RunSpeechThread, 0, 0, 0)))
	{
		//SetThreadPriority(g_main.hSpeechThread, THREAD_PRIORITY_ABOVE_NORMAL);
		WNotify(NULL, L"ERROR: Could not start speech thread.");
		ExitProc();
	    return FALSE;
	}

	// Determine default paths
	GetCurrentDirectory(MAX_PATH, g_paths.GetStartupPath());
	g_paths.SetWorkingPath(wstring(g_paths.GetStartupPath()));
	g_paths.ResetLastFile();

	// Load configuration from commmand line argument
	g_window.StartupConfigOpen();
	
	// Main message loop
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!g_keyInputDlg || !IsDialogMessage(g_keyInputDlg, &msg))
        {
			if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
        }
	}

	return (int) msg.wParam;
}

//! Wizard-generated code.
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_OWNDC;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_HXICON));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH) GetStockObject(BLACK_BRUSH);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_HXPLAYER);
	wcex.lpszClassName	= g_main.szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_HXICONSMALL));

	return RegisterClassEx(&wcex);
}

//****************************************************************************

//! Process window messages.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	switch (message)
    {
		case WM_COMMAND:
			wmId    = LOWORD(wParam);
			wmEvent = HIWORD(wParam);
			// Parse the menu selections
			if ((wmId >= IDM_ACCESSORY0) && (wmId <= IDM_ACCESSORYX)) // selecting from the accessory submenu
			{
				g_window.ClickModelRemove(wmId - IDM_ACCESSORY0);
			}
			else // all other menu selections
			switch (wmId)
			{
			case IDM_CONFIGOPEN:
				g_window.ClickConfigOpen();
				break;
			case IDM_CONFIGREOPEN:
				g_window.ClickConfigReopen();
				break;
			case IDM_CONFIGCLEAR:
				g_window.ClickConfigClear();
				break;
			case IDM_APPLYPARAMS:
				g_window.ClickParamOpen();
				break;
			case IDM_REAPPLYPARAMS:
				g_window.ClickParamReopen();
				break;
			case IDM_ACCESSORIZE_ADD:
				g_window.ClickModelAdd();
				break;
			case IDM_ACCESSORIZE_CLEAR:
				g_window.ClickModelClear();
				break;
			case IDM_MODELS_EXPORT:
				g_window.ClickModelsExport();
				break;
			case IDM_BACKGROUND:
				g_window.ClickBackgroundOpen();
				break;
			case IDM_CHANGEVOICE:
				g_window.ClickChangeVoice();
				break;
			case IDM_TTSENGINE_OPENMARY:
				g_window.SelectTTSEngine(IDM_TTSENGINE_OPENMARY);
				break;
			case IDM_TTSENGINE_MICROSOFTSAPI:
				g_window.SelectTTSEngine(IDM_TTSENGINE_MICROSOFTSAPI);
				break;
			case IDM_BACKGROUNDCLEAR:
				g_window.ClickBackgroundClear();
				break;
			case IDM_KEYINPUTDIALOG:
				g_window.ClickDirectSpeak();
				break;
			case IDM_SUBTITLEINPUT:
				g_window.ClickSubtitle(true);
				break;
			case IDM_SUBTITLEOUTPUT:
				g_window.ClickSubtitle(false);
				break;
			case IDM_PARSEINPUT:
				g_window.ClickParseInput();
				break;
			case IDM_BOS:
				g_window.ClickBreakOnStop();
				break;
			case IDM_ECHOINPUTS:
				g_window.ClickSetEchoInputs();
				break;
			case IDM_QUICKACTION_0:
				AsyncSpeak(g_config.quickAction0_speak);
				break;
			case IDM_QUICKACTION_1:
				AsyncSpeak(g_config.quickAction1_speak);
				break;
			case IDM_QUICKACTION_2:
				AsyncSpeak(g_config.quickAction2_speak);
				break;
			case IDM_QUICKACTION_3:
				AsyncSpeak(g_config.quickAction3_speak);
				break;
			case IDM_QUICKACTION_4:
				AsyncSpeak(g_config.quickAction4_speak);
				break;
			case IDM_QUICKACTION_5:
				AsyncSpeak(g_config.quickAction5_speak);
				break;
			case IDM_QUICKACTION_6:
				AsyncSpeak(g_config.quickAction6_speak);
				break;
			case IDM_QUICKACTION_7:
				AsyncSpeak(g_config.quickAction7_speak);
				break;
			case IDM_QUICKACTION_8:
				AsyncSpeak(g_config.quickAction8_speak);
				break;
			case IDM_QUICKACTION_9:
				AsyncSpeak(g_config.quickAction9_speak);
				break;
			case IDM_SYNAPSECOMMANDS:
			case IDM_SYNAPSEDIRECT:
			case IDM_SYNAPSEFACEMORPHS:
			case IDM_SYNAPSECAMERA:
				g_window.ClickSynapse(wmId);
				break;
			case IDM_SYNAPSEID:
				g_window.ClickSynapseID();
				break;
			case IDM_IDLEBEHAVIOUR:
				g_window.ClickSetIdle();
				break;
			case IDM_CAMERA_LOCK:
				g_window.ClickMouseMode(HXMM_LOCK);
				break;
			case IDM_CAMERA:
				g_window.ClickMouseMode(HXMM_CAMERA);
				break;
			case IDM_CAMERA_RESET:
				g_director.ResetRotation();
				g_director.ResetScale();
				g_director.ResetTranslation();
				break;
			case IDM_LIGHT:
				g_window.ClickMouseMode(HXMM_LIGHT);
				break;
			case IDM_LIGHT_RESET:
				g_director.ResetLights();
				break;
			case IDM_SUBTITLEPOSITION:
				g_window.ClickMouseMode(HXMM_SUBTITLE);
				break;
			case IDM_SUBTITLEPOSITION_RESET:
				g_director.ResetSubtitlePosition();
				break;
			case IDM_MOUSE_WINDOW:
				g_window.ClickMouseMode(HXMM_WINDOW);
				break;
			case IDM_WINDOW_BORDER:
				g_window.ClickWindowToggle(HXWT_BORDER);
				break;
			case IDM_WINDOW_MENU:
				g_window.ClickWindowToggle(HXWT_MENU);
				break;
			case IDM_WINDOW_MAXIMIZE:
				g_window.ClickWindowToggle(HXWT_MAXIMIZE);
				break;
			case IDM_WINDOW_FULLSCREEN:
				g_window.ClickWindowToggle(HXWT_FULLSCREEN);
				break;
			case IDM_WINDOW_ONTOP:
				g_window.ClickBringToTop();
				break;
			case IDM_WINDOW_TRANSPARENT:
				g_window.ClickWindowToggle(HXWT_TRANSPARENT);
				break;
			case IDM_TEST_MORPHS:
			case IDM_TEST_SPEECH:
				RunSimpleThread(wmId);
				break;
			case IDM_TEST_SPEECHFULL:
				AsyncSpeak(wstring(L"The thinking head is totally awesome"));
				break;
			case IDM_SHUTUP:
				AsyncSpeak(wstring(L""));
				break;
			case IDM_ABOUT:
				g_window.ClickAbout();
				break;
			case IDM_EXIT:
				DestroyWindow(g_window.GetMainHandle());
				break;
			default:
				return DefWindowProc(hWnd, message, wParam, lParam);
			}
			break;
		case WM_ERASEBKGND:
			if (g_director.IsRunning())
				return 1;
			else
				return DefWindowProc(hWnd, message, wParam, lParam);
		case WM_SIZE:
			SendMessage(g_window.GetStatHandle(), WM_SIZE, wParam, lParam);
			return HANDLE_WM_SIZE(hWnd, wParam, lParam, OnSize);

		HANDLE_MSG(hWnd, WM_DESTROY, OnDestroy);
		HANDLE_MSG(hWnd, WM_KEYDOWN, OnKeyDown);
		HANDLE_MSG(hWnd, WM_KEYUP, OnKeyUp);
		HANDLE_MSG(hWnd, WM_CHAR, OnKeyPressed);
        HANDLE_MSG(hWnd, WM_LBUTTONDOWN, OnLButtonDown);
        HANDLE_MSG(hWnd, WM_RBUTTONDOWN, OnRButtonDown);
		HANDLE_MSG(hWnd, WM_MBUTTONDOWN, OnMButtonDown);
        HANDLE_MSG(hWnd, WM_LBUTTONUP, OnLButtonUp);
        HANDLE_MSG(hWnd, WM_RBUTTONUP, OnRButtonUp);
		HANDLE_MSG(hWnd, WM_MBUTTONUP, OnMButtonUp);
        HANDLE_MSG(hWnd, WM_MOUSEMOVE, OnMouseMove);

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
	return 0;
}

//! Release resources.
void ExitProc()
{
	g_alive.Stop();
	g_main.Destroy();
	g_director.Stop();
	g_link.SetIsDead();
	g_window.Destroy();
	g_link.Disconnect();
}

//****************************************************************************

//! Exit program.
void OnDestroy(HWND hwnd)
{
	if (hwnd == g_window.GetMainHandle())
	{	
		ExitProc();	
		PostQuitMessage(0);
	}
}

//! Process size message.
void OnSize(HWND /*hwnd*/, UINT state, int cx, int cy)
{
    if (state == SIZE_MINIMIZED)
        return;

	g_director.SetOGLSize(cx,cy);
}

//****************************************************************************

//! Keyboard key down.
void OnKeyDown(HWND /*hwnd*/, UINT vk, BOOL /*fDown*/, int /*cRepeat*/, UINT /*flags*/)
{
	if (g_config.bAllowShortcutKeys)
	{
		if (GetKeyState(VK_CONTROL) & 0xF0) // CTRL-... messages
		{
			switch (vk)
			{
			case 'A':
				g_window.ClickModelAdd();
				break;
			case 'C':
				g_window.ClickModelClear();
				break;
			case 'E':
				g_window.ClickSetEchoInputs();
				break;
			case 'I':
				g_window.ClickSetIdle();
				break;
			case 13: // ENTER
				g_window.ClickDirectSpeak();
				break;
			case 32: // SPACE
				AsyncSpeak(wstring(L""));
				break;
			case 'L':
				g_window.ClickConfigOpen();
				break;
			case 'N':
				g_window.ClickWindowToggle(HXWT_TRANSPARENT);
				break;
			case 'O':
				g_window.ClickBringToTop();
				break;
			case 'P':
				g_window.ClickSubtitle(true);
				break;
			case 'S':
				g_window.ClickSubtitle(false);
				break;
			case 'T':
				AsyncSpeak(wstring(L"The thinking head is totally awesome"));
				break;
			case 'V':
				g_window.ClickChangeVoice();
				break;
			case 'X':
				DestroyWindow(g_window.GetMainHandle());
				break;
			case 'Y':
				g_window.ClickSynapseID();
				break;
			case '0':
				AsyncSpeak(g_config.quickAction0_speak);
				break;
			case '1':
				AsyncSpeak(g_config.quickAction1_speak);
				break;
			case '2':
				AsyncSpeak(g_config.quickAction2_speak);
				break;
			case '3':
				AsyncSpeak(g_config.quickAction3_speak);
				break;
			case '4':
				AsyncSpeak(g_config.quickAction4_speak);
				break;
			case '5':
				AsyncSpeak(g_config.quickAction5_speak);
				break;
			case '6':
				AsyncSpeak(g_config.quickAction6_speak);
				break;
			case '7':
				AsyncSpeak(g_config.quickAction7_speak);
				break;
			case '8':
				AsyncSpeak(g_config.quickAction8_speak);
				break;
			case '9':
				AsyncSpeak(g_config.quickAction9_speak);
				break;
			case 219: // '['
				g_window.ClickMouseMode(HXMM_CAMERA);
				break;
			case 221: // ']'
				g_window.ClickMouseMode(HXMM_LIGHT);
				break;
			case 220: // '\'
				g_window.ClickMouseMode(HXMM_WINDOW);
				break;
			default:
				#ifdef _DEBUG
				g_window.ShowStatus(L"Ctrl + Key pressed: %c (%d)", vk, vk);
				#endif
				break;
			}
		}
		else
		{
			#ifdef _DEBUG
			g_window.ShowStatus(L"Key down: %c (%d)", vk, vk);
			#endif
		}
	}
	g_link.SetKeyDown((int)vk);
}

//! Keyboard key up.
void OnKeyUp(HWND /*hwnd*/, UINT vk, BOOL /*fDown*/, int /*cRepeat*/, UINT /*flags*/)
{
	if (g_config.bAllowShortcutKeys)
	{
		switch (vk)
		{
			case 27: // Esc
				g_window.PressEscape();
				break;
			case 116: // F5
				g_window.ClickConfigReopen();
				break;
			case 117: // F6
				g_window.ClickParamReopen();
				break;
			case 122: // F11
				g_window.ClickWindowToggle(HXWT_MENU);
				break;
			case 123: // F12
				g_window.ClickWindowToggle(HXWT_MAXIMIZE);
				break;
			case 124: // F13
			case 145: // Scroll Lock
				g_window.ClickWindowToggle(HXWT_FULLSCREEN);
				break;
		}
	}
	g_link.SetKeyDown(0);
}

//! Keyboard key pressed.
void OnKeyPressed(HWND /*hwnd*/, unsigned short charcode, int /*keydata*/)
{
	if (g_config.bSubtitleInput)
	{
		if ((charcode >= 'a'  && charcode <='z')  ||
			(charcode >= 'A'  && charcode <='Z')  ||
			(charcode >= '0'  && charcode <='9')  ||
			 charcode == '{'  || charcode == '}'  ||
		 	 charcode == '.'  || charcode == ','  ||
			 charcode == '?'  || charcode == '&'  ||
			 charcode == ':'  || charcode == ';'  ||
			 charcode == '!'  || charcode == '+'  ||
			 charcode == '-'  || charcode == '('  ||
			 charcode == ')'  || charcode == '\"' ||
			 charcode == '\'' || charcode == '%'  || 
			 charcode == '@'  || charcode == '='  ||
			 charcode == '|'  || charcode == ' ')
			{
				g_subtitle.PromptKey(static_cast<char>(charcode));
			} 
			else if (charcode == 8 || charcode == 46)
			{
				g_subtitle.PromptBack();
			}
			else if (charcode == 13)
			{
				g_subtitle.PromptEnter();
			}
		g_subtitle.Generate(); // make sure it shows on screen
	}
	g_link.SetLastKey((int)charcode);
}

//****************************************************************************

//! Left mouse button down.
void OnLButtonDown(HWND hwnd, BOOL /*dblClk*/, int x, int y, UINT /*flag*/)
{
    g_mouse.oldX = x;
    g_mouse.oldY = y;
	g_mouse.startX = x;
	g_mouse.startY = y;
	g_mouse.clicked = true;
    if (GetCapture() != hwnd)
        SetCapture(hwnd);
	g_link.SetMouseDown(g_link.GetMouseDown() | HXSY_LEFTMOUSEDOWN);
	g_link.SetMouseDownXY(x, y);
	g_window.ShowStatus(L"");
}

//! Right mouse button down.
void OnRButtonDown(HWND hwnd, BOOL /*dblClk*/, int x, int y, UINT /*flag*/)
{
    g_mouse.oldX = x;
    g_mouse.oldY = y;
    if (GetCapture() != hwnd)
        SetCapture(hwnd);
	g_link.SetMouseDown(g_link.GetMouseDown() | HXSY_RIGHTMOUSEDOWN);
	g_link.SetMouseDownXY(x, y);
	g_window.ShowStatus(L"");
}

//! Middle mouse button down.
void OnMButtonDown(HWND hwnd, BOOL /*dblClk*/, int x, int y, UINT /*flag*/)
{
    g_mouse.oldX = x;
    g_mouse.oldY = y;
    if (GetCapture() != hwnd)
        SetCapture(hwnd);
	g_link.SetMouseDown(g_link.GetMouseDown() | HXSY_MIDDLEMOUSEDOWN);
	g_link.SetMouseDownXY(x, y);
	g_window.ShowStatus(L"");
}

//! Left mouse button up.
void OnLButtonUp(HWND /*hwnd*/, int /*x*/, int /*y*/, UINT keyFlags)
{
    if ((keyFlags & MK_RBUTTON) != MK_RBUTTON)
        ReleaseCapture();
	g_mouse.clicked = false;
	g_link.SetMouseDown(g_link.GetMouseDown() & ~HXSY_LEFTMOUSEDOWN);
	g_link.SetLastMouse(HXSY_LEFTMOUSEDOWN);
}

//! Right mouse button up.
void OnRButtonUp(HWND /*hwnd*/, int /*x*/, int /*y*/, UINT keyFlags)
{
    if ((keyFlags & MK_LBUTTON) != MK_LBUTTON)
        ReleaseCapture();
	g_link.SetMouseDown(g_link.GetMouseDown() & ~HXSY_RIGHTMOUSEDOWN);
	g_link.SetLastMouse(HXSY_RIGHTMOUSEDOWN);
}

//! Middle mouse button down.
void OnMButtonUp(HWND /*hwnd*/, int /*x*/, int /*y*/, UINT keyFlags)
{
    if ((keyFlags & MK_MBUTTON) != MK_MBUTTON)
        ReleaseCapture();
	g_link.SetMouseDown(g_link.GetMouseDown() & ~HXSY_MIDDLEMOUSEDOWN);
	g_link.SetLastMouse(HXSY_MIDDLEMOUSEDOWN);
}

//! Mouse move.
void OnMouseMove(HWND /*hwnd*/, int x, int y, UINT keyFlags)
{
    if (((keyFlags & MK_LBUTTON) == MK_LBUTTON) && g_mouse.clicked)
    {
		switch (g_mouse.headMouseMode)
		{
		case HXMM_CAMERA:
			if ((GetKeyState(VK_LSHIFT) & 0xF0) || (GetKeyState(VK_RSHIFT) & 0xF0))
			{
				float uniScale = -static_cast<float>(y - g_mouse.oldY)/300.0f;
				g_director.Scale(uniScale, uniScale, uniScale);
			}
			else
			{
				g_director.Rotate(static_cast<float>(x - g_mouse.oldX)/300.0f, static_cast<float>(y - g_mouse.oldY)/300.0f, 0);
			}
			break;
		case HXMM_LIGHT:
			if ((GetKeyState(VK_LSHIFT) & 0xF0) || (GetKeyState(VK_RSHIFT) & 0xF0))
				g_director.RotateLight(1, static_cast<float>(x - g_mouse.oldX)/200.0f, static_cast<float>(y - g_mouse.oldY)/200.0f);
			else
				g_director.RotateLight(0, static_cast<float>(x - g_mouse.oldX)/200.0f, static_cast<float>(y - g_mouse.oldY)/200.0f);
			break;
		case HXMM_SUBTITLE:
			g_director.MoveSubtitlePosition(static_cast<float>(x - g_mouse.oldX), -static_cast<float>(y - g_mouse.oldY), 0.0f);
			break;
		case HXMM_WINDOW:
			g_window.RelativeReposition(x - g_mouse.oldX, y - g_mouse.oldY);
			g_window.WindowStatus();
			return; // changed frame of reference (don't update old mouse coords)
		case HXMM_LOCK:
		default:
			break;
		}
    }
    else if ((keyFlags & MK_RBUTTON) == MK_RBUTTON)
    {
		switch (g_mouse.headMouseMode)
		{
		case HXMM_CAMERA:
			if ((GetKeyState(VK_LSHIFT) & 0xF0) || (GetKeyState(VK_RSHIFT) & 0xF0))
				g_director.Translate(0, 0, static_cast<float>(y - g_mouse.oldY)/2.0f);
			else
				g_director.Translate(static_cast<float>(x - g_mouse.oldX)/3.0f, -static_cast<float>(y - g_mouse.oldY)/3.0f, 0);
			break;
		case HXMM_LIGHT:
			if ((GetKeyState(VK_LSHIFT) & 0xF0) || (GetKeyState(VK_RSHIFT) & 0xF0))
				g_director.LightIntensity(1, 0.0f, -0.001f * static_cast<float>(y - g_mouse.oldY));
			else
				g_director.LightIntensity(0, 0.0f, -0.001f * static_cast<float>(y - g_mouse.oldY));
			break;
		case HXMM_SUBTITLE:
			g_director.MoveSubtitlePosition(0.0f, 0.0f, static_cast<float>(y - g_mouse.oldY));
			break;
		case HXMM_WINDOW:
			g_window.RelativeResize(x - g_mouse.oldX, y - g_mouse.oldY);
			g_window.WindowStatus();
			break;
		case HXMM_LOCK:
		default:
			break;
		}
    }
	else if ((keyFlags & MK_MBUTTON) == MK_MBUTTON)
    {
		switch (g_mouse.headMouseMode)
		{
		case HXMM_CAMERA:
			if ((GetKeyState(VK_LSHIFT) & 0xF0) || (GetKeyState(VK_RSHIFT) & 0xF0))
				g_director.Scale(static_cast<float>(x - g_mouse.oldX)/300.0f, static_cast<float>(y - g_mouse.oldY)/300.0f, 0.0f);
			else
				g_director.Rotate(0, 0, static_cast<float>(x - g_mouse.oldX)/300.0f);
			break;
		case HXMM_LIGHT:
			if ((GetKeyState(VK_LSHIFT) & 0xF0) || (GetKeyState(VK_RSHIFT) & 0xF0))
				g_director.LightIntensity(1, -0.001f * static_cast<float>(y - g_mouse.oldY), 0.0f);
			else
				g_director.LightIntensity(0, -0.001f * static_cast<float>(y - g_mouse.oldY), 0.0f);
			break;
		case HXMM_LOCK:
		default:
			break;
		}
    }

	g_mouse.oldX = x;
	g_mouse.oldY = y;
}

//****************************************************************************

//! Create a thread to run through various simple features of Head X.
void RunSimpleThread(int menu_id)
{
	DWORD threadID;
	if (g_main.hAuxThread)
	{
		g_main.doAuxExit = true;
		WaitForSingleObject(g_main.hAuxThread, INFINITE);
		CloseHandle(g_main.hAuxThread);
		g_main.hAuxThread = NULL;
		g_main.doAuxExit = false;
	}

	int* pid = new int;
	*pid = menu_id;

	if (!(g_main.hAuxThread = CreateThread( 
                     NULL,       // default security attributes
                     0,          // default stack size
                     (LPTHREAD_START_ROUTINE) SimpleThread, 
                     (LPVOID)pid,// thread function argument
                     0,          // default creation flags
                     &threadID))) // receive thread identifier	
	{
		delete pid;
	}
}

//! Automatically run through a simple feature of Head X. (thread routine)
DWORD WINAPI SimpleThread(LPVOID lpParam) 
{
	int* pid = (int*)lpParam;
	switch (*pid)
	{
	case IDM_CONFIGOPEN:
		g_director.AsyncExecuteP(L"loadconfig %s", g_paths.GetStartupFile());
		while (!g_link.GetIsReady()) { Sleep(10); }
		g_paths.SetWorkingPath(wstring(g_paths.GetStartupPath()));
		break;
	case IDM_TEST_MORPHS:
		if (g_director.GetDisplay() && g_director.GetDisplay()->MorphCount())
		{
			vector<wstring> labels;
			g_director.GetDisplay()->GetMorphLabels(labels);
			for (size_t i = 0; i < labels.size(); i++)
			{
				g_director.PlayMorph(labels[i], 0.5f, 1);
			}
		}
		break;
	case IDM_TEST_SPEECH:
		SyncSpeak(L"The thinking head is totally awesome", NULL);
		break;
	}
	delete pid;
	return 0;
}

//****************************************************************************

//! Speak the passed text. (thread routine)
DWORD WINAPI RunSpeechThread(LPVOID /*lpParam*/) 
{
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	while (!g_main.doSpeechExit)
	{
		WaitForSingleObject(g_main.hSpeechMutex, INFINITE);
		if (g_main.bSpeakNow)
		{
			g_main.bSpeakNow = false;
			wstring commandCopy = g_main.commandString;
			if (!g_main.bBlock) {
				ReleaseMutex(g_main.hSpeechMutex);
				if (!g_main.doSpeechExit) SyncSpeak(commandCopy);
			} else {
				if (!g_main.doSpeechExit) SyncSpeak(commandCopy);
				ReleaseMutex(g_main.hSpeechMutex);
			}
		}
		else
		{
			ReleaseMutex(g_main.hSpeechMutex);
			Sleep(g_director.GetFrameTime());
		}
	}
	CoUninitialize();
	return 0;
}

//! Speak a text and/or process commands.
void AsyncSpeak(const wstring& text, bool block)
{
	WaitForSingleObject(g_main.hSpeechMutex, INFINITE);
	g_logger.Log(text, L'C');
	while (g_main.bSpeakNow)
	{
		ReleaseMutex(g_main.hSpeechMutex);
		Sleep(g_director.GetFrameTime());
		WaitForSingleObject(g_main.hSpeechMutex, INFINITE);
	}
	g_main.commandString = text;
	g_main.bSpeakNow = true;
	g_main.bBlock = block;
	ReleaseMutex(g_main.hSpeechMutex);

	/*while (g_main.bSpeakNow)
	{
		Sleep(g_director.GetFrameTime());
	}
	g_main.commandString = text;
	g_main.bSpeakNow = true;*/
}

//! Speak the passed text (on the active thread and default render target).
void SyncSpeak(const wstring& text)
{
	SyncSpeak(text, g_director.GetDisplay());
}

//! Speak the passed text (on the active thread and specified render target).
void SyncSpeak(const wstring& text, HXDisplay* pDisplay)
{
	//WaitForSingleObject(g_main.hSpeechMutex, INFINITE);
	//g_logger.Log(text, L'S');
	g_speechtts.SetDisplay(pDisplay);
	g_talk.SetSpeechClient(&g_speechtts, &g_main.bSpeakNow);
	if (g_config.bCurlyTags)
		g_talk.Speak(text.c_str());
	else
	{
		g_talk.SpeakDirect(text.c_str());
	}
	if (!g_main.bSpeakNow)
		g_link.SetIsTalking(HXSY_NONE);
	if (text.empty())
		g_window.ShowStatus(L"Spoken text: (nothing)");
	else
		g_window.ShowStatus(L"Spoken text: %s", text.c_str());
	//ReleaseMutex(g_main.hSpeechMutex);
}

//-----------------------------------------------------------------------------
